﻿using System;
using System.Configuration;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Collections.Concurrent;
using RAD.LightOne.Cache;
using RAD.LightOne;
using System.Drawing;
using System.IO;
using System.Drawing.Imaging;
using System.Threading;

namespace RAD.LightOne.Data
{
#if !OLD_MODE
    #region Documentation
    /// <summary>
    /// Classe que provê funcionalidades de acesso a banco de dados e operções no banco.
    /// </summary>
    #endregion Documentation
    public sealed class DBEngine :  IDisposable, IDbConnection, IDbCommand
    {
        #region Fields

        const string CHR_MNEMO = "$";
        //private volatile static MyDbTransaction scopedTransaction;
        private static ConcurrentDictionary<int, MyDbTransaction> scopedTransactions;
        private static ConnStringMode mode;
        private static ConnectionStringSettings setting;
        private static DbProviderFactory dbProviderFactory;
        private static object locker;
        private  WeakReference<MyDbTransaction> trx;

        private static ConcurrentDictionary<Thread, DBEngine> threadContextEngine;

        #region IDbConnection required

        private IDbConnection conn;

        #endregion

        #region IDbCommand required

        private IDbCommand command;

        #endregion

        #endregion Fields

        #region Properties
        #region Documentation
        ///<summary>
        /// Define/Retorna o provider dbProviderFactory utilizado ou configurado.
        ///</summary>
        #endregion
        public static DbProviderFactory DbProviderFactory
        {
            get { return DBEngine.dbProviderFactory; }
            set { DBEngine.dbProviderFactory = value; }
        }
        #region Documentation
        ///<summary>
        /// Define/Retorna o modo de operação do engine quanto à obtenção das configurações de banco de dados.
        ///</summary>
        #endregion
        public static ConnStringMode Mode
        {
            get { return DBEngine.mode; }
            set { DBEngine.mode = value; }
        }
        #region Documentation
        ///<summary>
        /// Define/Retorna deve ser definida em caso de modo programático definido em "Mode".
        ///</summary>
        #endregion
        public static ConnectionStringSettings Setting
        {
            get { return DBEngine.setting; }
            set { DBEngine.setting = value; }
        }

        #region Documentation
        /// <summary>
        /// Retorna se o DBEngine é transacionado ou não, ou seja, se foi criado recebendo uma transação.
        /// </summary>
        #endregion Documentation
        public bool IsTransactioned
        {
            get
            {
                return (this.command.Transaction != null);
            }
        }

        #region Documentation
        /// <summary>
        /// Retorna a conexão interna, real contida por este DBEngine
        /// </summary>
        #endregion
        public DbConnection InnerConnection
        {
            get
            {
                return this.conn as DbConnection;
            }
        }

        #endregion Properties

        #region Constructors

        static DBEngine()
        {
            scopedTransactions = new ConcurrentDictionary<int,MyDbTransaction>();
            threadContextEngine = new ConcurrentDictionary<Thread, DBEngine>();
            locker = new object();
        }

        #region Documentation
        /// <summary>
        /// Construtor que em si já abre a conexão a ser utilizada, que é o próprio DBEngine.
        /// </summary>
        #endregion Documentation
        internal DBEngine(DbConnection conn)
        {
            try
            {
                this.conn = conn;
                
                if (conn.State != ConnectionState.Open) this.conn.Open();
                this.command = conn.CreateCommand();
                this.TreatOracleThings();
                this.InnerConnection.Disposed +=new EventHandler(InnerConnection_Disposed);
            }
            catch (Exception ex)
            {
                throw new ConfigurationErrorsException("LightOne unable to get connection by provider.", ex);
            }
        }

        #region Documentation
        /// <summary>
        /// Construtor requerido para reaproveitamento de conexões de forma transparente e evitando
        /// encapsulamento redundante de transações, se houverem.
        /// </summary>
        /// <param name="conn">DBEngien que é em si uma conexão.</param>
        #endregion
        internal DBEngine(DBEngine conn)
        {
            try
            {
                this.conn = conn.Connection;
                if (conn.State != ConnectionState.Open) this.conn.Open();
                this.command = this.conn.CreateCommand();
                this.TreatOracleThings();
            }
            catch (Exception ex)
            {
                throw new ConfigurationErrorsException("LightOne unable to get connection by provider.", ex);
            }
        }

        #region Documentation
        /// <summary>
        /// Construtor para operações transacionadas.
        /// </summary>
        /// <param name="trx">transação corrente.</param>
        #endregion
        internal DBEngine(IDbTransaction trx) 
        {
            this.conn = trx.Connection;
            this.command = this.conn.CreateCommand();
            this.TreatOracleThings();
            this.Transaction = trx;
        }

        #region Documentation
        /// <summary>
        /// Destrutor de DBEngine.
        /// </summary>
        #endregion Dcoumentation
        ~DBEngine() 
        {
            this.command = null;
            this.conn = null;
        }
        #endregion Constructors

        #region Methods

#if ORACLE
        #region Documentation
        ///<summary>
        /// Método criado especificamente para tratar ajustes específicos para utilização do Oracle.DataAccess.Client e seus comportamentos
        /// que na maioria das vezes está na contra-mão do comportamento esperado por todos os outros providers.
        ///</summary>
        #endregion
        private void TreatOracleThings()
        {
            #region Comments

            /*
                 * Este trecho altera o comportamento padrão esperado pelo driver da Oracle, ao contrário de todos os demais,
                 * o driver Oracle não mapeia os parâmetros nomeados pelo nome por padrão e sim em forma de sequencia.
                 * Para obter o comportamento esperado é necessário definir "BindByName" como "true". Tal propriedade não é
                 * padrão em System.Data.IDbCommand ou System.Data.Common.DbCommand, isto exige que tal propriedade, e apenas para Oracle,
                 * seja feita por reflexão.
                 */

            #endregion
            try
            {
                if (this.command.GetType().FullName.Contains("Oracle.DataAccess.Client"))
                {
                    Type type = this.command.GetType();
                    var info = type.GetProperty("BindByName");
                    info.SetValue(this.command, true, null);
                }
            }
            catch { }
        }
#else
        private void TreatOracleThings()
        {
        }

#endif

        #region Documentation
        /// <summary>
        /// Retorna uma instância da Engine de acesso ao banco de dados.
        /// </summary>
        #endregion
        public static DBEngine GetInstance()
        {
            lock (locker)
            {
                if (Mode == ConnStringMode.Programmatically && Setting == null)
                {
                    throw new ConfigurationErrorsException("The mode is programmatically, provide a valid ConnectionStringSetting by code.");
                }

                DBEngine engine;
                if (threadContextEngine.ContainsKey(Thread.CurrentThread))
                {
                    engine = threadContextEngine[Thread.CurrentThread];
                    return engine;
                }
                //if (scopedTransaction != null)
                //{
                //    return scopedTransaction.Dbe;
                //}
                else if (mode == ConnStringMode.Programmatically)
                {
                    engine = DBEngineFactory.GetEngine(Setting);

                }
                else
                {
                    engine = DBEngineFactory.GetEngine();
                }
                while (!threadContextEngine.TryAdd(Thread.CurrentThread, engine)) { }
                return engine;
            }
        }

        private void InnerConnection_Disposed(object sender, EventArgs e)
        {
            DBEngine outer = null;
            while(!threadContextEngine.TryRemove(Thread.CurrentThread, out outer)){}
        }

        #region Documentation
        /// <summary>
        /// Retorna a instância da engine de banco de dados, vinculada à transação passada no parâmetro.
        /// </summary>
        #endregion
        public static DBEngine GetInstance(IDbTransaction trx)
        {
            lock (locker)
            {
                if (scopedTransactions.ContainsKey(trx.Connection.GetHashCode()) && scopedTransactions[trx.Connection.GetHashCode()] != null)
                {
                    return scopedTransactions[trx.Connection.GetHashCode()].Dbe;
                }
                else if (trx is MyDbTransaction)
                {
                    return DBEngineFactory.GetEngine(trx as MyDbTransaction);
                }
                else
                {
                    throw new InvalidCastException("The parameter trx don't be cast to correct type.");
                }
            }
        }

        #region Documentation
        /// <summary>
        /// Varre recursivamente os parâmetros passados em busca de arrays em seu interior de forma a formar um array unico de dimensão simples.
        /// </summary>
        #endregion
        private object[] PreProcessArgs(object[] args)
        {
            List<object> result = new List<object>();

            foreach (object a in args)
            {
                if (a == null)
                {
                    result.Add(null);
                }
                else if (a.GetType().IsArray)
                {
                    result.AddRange(PreProcessArgs((object[]) a));
                }
                else
                {
                    result.Add(a);
                }
            }
            return result.ToArray();
        }


        #region Documentation
        /// <summary>
        /// Recebe uma instrução sql e os parâmetros que devem ser concatenados a ela e 
        /// prepara o comando para ser executado.
        /// </summary>
        /// <param name="sql">instrução sql.</param>
        /// <param name="args">argumentos a concatenar</param>
        #endregion
        private void Prepare(ref string sql, params object[] args)
        {
            this.Parameters.Clear();
            Configuration config = Configuration.Instance();

            sql = EvalReplaceSchema(sql);

            if (args.Length == 0)
            {
                this.CommandText = sql;
            }
            else
            {
                object[] values = PreProcessArgs(args);
                List<string> paramsNames = new List<string>();
                for (int i = 0; i < values.Length; i++)
                {
                    if (!sql.Contains("{" + i.ToString() + "}")) throw new IndexOutOfRangeException("MyObject.Prepare(sql, args).\nÍndice do argumento inexistente na expressão sql.");
                    IDbDataParameter param = this.command.CreateParameter();
                    param.ParameterName = config.ParameterPrefix + "p" + i.ToString();
                    switch (config.Transform)
                    {
                        case SqlCaseTransform.Upper:
                            param.ParameterName = param.ParameterName.ToUpper();
                            break;
                        case SqlCaseTransform.Lower:
                            param.ParameterName = param.ParameterName.ToLower();
                            break;
                        default:
                            break;
                    }
                    this.DefineParameter(param, values[i]);
                    paramsNames.Add(param.ParameterName);
                    this.command.Parameters.Add(param);
                }
                try
                {
                    StringBuilder paramParser = new StringBuilder();
                    this.CommandText = paramParser.AppendFormat(sql, paramsNames.ToArray()).ToString();
                }
                catch (Exception except)
                {
                    throw new IndexOutOfRangeException("MyObject.Prepare(sql, args).\n" + except.Message, except);
                }
            }
            
        }

        #region Documentation
        /// <summary>
        /// Avalia a existência e necessidade de substituir o mnemônico '$' pelo SchemaPrefix definido no arquivo de configuração.
        /// </summary>
        /// <param name="sql"></param>
        /// <param name="config"></param>
        /// <returns></returns>
        #endregion
        internal static string EvalReplaceSchema(string sql)
        {
            Configuration config = Configuration.Instance();
            //substituição do mneônico pelo prefixo de schema caso exista.
            if (!string.IsNullOrWhiteSpace(config.SchemaPrefix))
            {
                StringBuilder builder = new StringBuilder(sql);
                builder.Replace(CHR_MNEMO, config.SchemaPrefix + ".");
                return builder.ToString();
            }
            else
            {
                return sql.Replace(CHR_MNEMO, "");
            }
            
        }

        #region Documentation
        /// <summary>
        /// Avalia a existência e necessidade de substituir o mnemônico '$' pelo SchemaPrefix definido no arquivo de configuração.
        /// </summary>
        /// <param name="sql"></param>
        /// <param name="config"></param>
        /// <returns></returns>
        #endregion
        internal static void EvalReplaceSchema(StringBuilder builder)
        {
            Configuration config = Configuration.Instance();
            if (!string.IsNullOrWhiteSpace(config.SchemaPrefix))
            {
                builder.Replace(CHR_MNEMO, config.SchemaPrefix + ".");
            }
            else
            {
                builder.Replace(CHR_MNEMO, "");
            }
        }

        #region Documentation
        /// <summary>
        /// Preenche um DataTable com o schema resultante do comando SQl "Select" passado
        /// ao DBEngine.
        /// </summary>
        /// <param name="table">DataTable que será preenchido com dados</param>
        #endregion
        internal DataTable ResolveSchema(string tableName)
        {
            try
            {
                DataTable dt = new DataTable(tableName);
                DbDataAdapter adapt = DBEngineFactory.Factory.CreateDataAdapter();
                adapt.SelectCommand = (DbCommand)this.command;
                adapt.SelectCommand.Prepare();
                adapt.FillSchema(dt, SchemaType.Source);
                return dt;
            }
            catch (Exception ex)
            {
                throw new InvalidProgramException("Unable to get the table schema.", ex);
            }
        }

        #region Documentation
        /// <summary>
        /// Executa um comando de retorno único, como count, sum , etc... comportamento
        /// transacionado ou não, de acordo com o construtor da classe.
        /// </summary>
        /// <param name="sql">instrução sql parametrizada com parâmetros {1..n}.</param>
        /// <param name="args">argumentos dos parâmetros.</param>
        #endregion
        public object ExecuteScalar(string sql, params object[] args)
        {
            try
            {
                this.Prepare(ref sql, args);
                return this.command.ExecuteScalar();
            }
            catch (Exception except)
            {
                throw except;
            }
        }

        #region Documentation
        /// <summary>
        /// Executa um IDataReader e o retorna, transacionado ou não, de acordo com o construtor da classe.
        /// </summary>
        /// <param name="sql">instrução sql parametrizada com parâmetros {1..n}.</param>
        /// <param name="args">argumentos dos parâmetros.</param>
        #endregion
        public IDataReader ExecuteReader(string sql, params object[] args)
        {
            //lock (locker)
            {
                this.Prepare(ref sql, args);
                return this.ExecuteReader();
            }
        }

        #region Documentation
        /// <summary>
        /// Preenche o <see cref="DataTable"/> passado no parâmetro com o resultado da expressão SQL.
        /// </summary>
        #endregion
        public void Fill(DataTable dataTable, string sql, params object[] args)
        {
            if (dataTable == null) throw new ArgumentNullException("dataTable");
            this.Prepare(ref sql, args);
            DbDataAdapter adapt = DBEngineFactory.Factory.CreateDataAdapter();
            adapt.SelectCommand = (DbCommand)this.command;
            adapt.Fill(dataTable);
        }

        #region Documentation
        /// <summary>
        /// Preenche o <see cref="DataTable"/> passado no parâmetro com o resultado da expressão SQL e um <see cref="DataTable"/> adicional com o schema do resultado.
        /// </summary>
        #endregion
        public void Fill(DataTable dataTable, DataTable schemaTable, string sql, params object[] args)
        {
            if (dataTable == null) throw new ArgumentNullException("dataTable");
            if (schemaTable == null) throw new ArgumentNullException("schemaTable");
            this.Prepare(ref sql, args);
            DbDataAdapter adapt = DBEngineFactory.Factory.CreateDataAdapter();
            adapt.SelectCommand = (DbCommand)this.command;
            adapt.Fill(dataTable);
            adapt.FillSchema(schemaTable, SchemaType.Source);
        }

        #region Documentation
        /// <summary>
        /// Executa uma instrução sql sem retorno, "não select" e retorna o número de registros afetados.
        /// </summary>
        /// <param name="sql">instrução sql a ser executada.</param>
        /// <param name="args">argumentos a serem concatenados.</param>
        #endregion
        public int ExecuteNonQuery(string sql, params object[] args)
        {
            this.Prepare(ref sql, args);
            return this.command.ExecuteNonQuery();
        }

        #region Documentation
        /// <summary>
        /// Insere os dados, persiste em banco, os dados da instância passada.
        /// </summary>
        /// <param name="instance">instância a ser persistida.</param>
        /// <param name="trx">transação de banco utilizada para a operação.</param>
        #endregion
        internal void Insert(MyObject instance, IDbTransaction trx)
        {
            instance.RefreshLastPersistence();

            this.Transaction = trx;

            InternalObj item = InternalObjBuilder.GetWriteStructure<MyObject>(instance, DataOperations.Insert);
            InternalObjProcessor.Standardize(item, trx);
            foreach (Table tItem in item)
            {
                StringBuilder sql = new StringBuilder();

                this.Parameters.Clear();

                StringBuilder fields = new StringBuilder();
                StringBuilder parms = new StringBuilder();

                foreach (Field fItem in tItem)
                {
                    fields.Append((fields.Length == 0 ? "" + fItem.Name : "," + fItem.Name));
                    parms.Append((parms.Length == 0 ? "" + fItem.GetParameterName() : "," + fItem.GetParameterName()));
                    
                    IDbDataParameter param = this.CreateParameter();
                    param.ParameterName = fItem.GetParameterName();
                    try
                    {
                        this.DefineParameter(param, fItem);
                        this.Parameters.Add(param);
                    }
                    catch (ArgumentException except)//claro que este tratamento é tão somente para o Oracle.
                    {
                        if (fItem.GetType() == typeof(bool))
                        {
                            fItem.Value = Convert.ToByte(fItem.Value);
                            this.DefineParameter(param, fItem);
                            this.Parameters.Add(param);
                        }
                        else
                        {
                            throw except;
                        }
                    }
                }
                sql.AppendFormat("INSERT INTO {0}({1}) VALUES({2})", tItem.Name, fields.ToString(), parms.ToString());
                DBEngine.EvalReplaceSchema(sql);
                this.CommandText = sql.ToString();
                this.ExecuteNonQuery();
            }

        }

        #region Documentation
        /// <summary>
        /// Método auxiliar a UPDATE e INSERT uma vez que avalia um trecho comum a ambos e define 
        /// o comportamento em objetos Lazy.
        /// Pré-processa o tipo de um parâmetro e redefine-o para seu tipo primitivo em caso de valores enumerados.
        /// </summary>
        /// <remarks>
        /// A redefinição do tipo enumerado ao seu tipo primitivo se deve à incapacidade dos drivers do Oracle em lidar com
        /// tais tipos de forma eficiente, gera erros, o que não acontece com os drivers do Sql Server e MySql por exemplo,
        /// mas que pode vir a acontecer com um outro driver qualquer, embora eu duvide. Enfim, tem coisas que só o Oracle faz pra vc.
        /// </remarks>
        #endregion Documentation
        private void DefineParameter(IDbDataParameter param, Field fItem)
        {
            if (fItem.Value == null)
            {
                param.Value = DBNull.Value;
            }
            else if (fItem.GetType().IsEnum)
            {
                Type undelying = Enum.GetUnderlyingType(fItem.GetType());
                fItem.Value = Convert.ChangeType(fItem.Value, undelying);
                param.Value = fItem.Value;
            }
            else if (Lazy.IsILazy(fItem.Value.GetType()))
            {
                param.Value = ((ILazy)fItem.Value).Id.ToString();
            }
            else if (fItem.Value is MyObject)
            {
                param.Value = ((MyObject)fItem.Value).Id.ToString();
            }
            else if (fItem.Value is System.Drawing.Image)
            {
                if (fItem.Value != null)
                {
                    MemoryStream mem = new MemoryStream();
                    Image img = (Image)fItem.Value;
                    img.Save(mem, img.RawFormat);
                    param.Value = mem.ToArray();
                }
                else
                {
                    param.Value = new byte[0];
                }
            }
            else
            {
                param.Value = fItem.Value;
            }
        }

        #region Documentation
        /// <summary>
        /// Método auxiliar a Prepare, pré-processa o tipo de um parâmetro e redefine-o para seu tipo primitivo em caso de valores enumerados.
        /// </summary>
        /// <remarks>
        /// A redefinição do tipo enumerado ao seu tipo primitivo se deve à incapacidade dos drivers do Oracle em lidar com
        /// tais tipos de forma eficiente, gera erros, o que não acontece com os drivers do Sql Server e MySql por exemplo,
        /// mas que pode vir a acontecer com um outro driver qualquer, embora eu duvide. Enfim, tem coisas que só o Oracle faz pra vc.
        /// </remarks>
        #endregion Documentation
        private void DefineParameter(IDbDataParameter param, object value)   
        {
            if (value == null)
            {
                param.Value = DBNull.Value;
            }
            else if (value.GetType().IsEnum)
            {
                Type undelying = Enum.GetUnderlyingType(value.GetType());
                value = Convert.ChangeType(value, undelying);
                param.Value = value;
            }
            else if (Lazy.IsILazy(value.GetType()))
            {
                param.Value = ((ILazy)value).Id.ToString();
            }
            else if (value is IMyObject)
            {
                param.Value = ((IMyObject)value).Id.ToString();
            }
            else
            {
                param.Value = value;
            }
        }

        #region Documentation
        /// <summary>
        /// Atualiza os dados de uma instância no banco de dados.
        /// </summary>
        /// <param name="instance">Instância que será atualizada.</param>
        /// <param name="trx">Transação utilizada na operação.</param>
        #endregion Documentation
        internal void Update(MyObject instance, IDbTransaction trx)
        {
            DateTime previousPersistence = instance.LastPersistence;
            instance.RefreshLastPersistence();

            this.Transaction = trx;

            bool optimisticFieldOut = true;
            bool idFinded = false;

            InternalObj item = InternalObjBuilder.GetWriteStructure<MyObject>(instance, DataOperations.Update);
            InternalObjProcessor.Standardize(item, trx);
            foreach (Table tItem in item)
            {
                Type type = instance.GetType();
                IdAttribute idAttr = IdAttribute.GetDefinedAttribute(type);
                LastPersistenceAttribute lastPAttr = LastPersistenceAttribute.GetDefinedAttribute(type);

                StringBuilder preSql = new StringBuilder("UPDATE {0} SET {1} WHERE " + idAttr.FieldName + "={2}");
                this.Parameters.Clear();

                StringBuilder fields = new StringBuilder();

                foreach (Field fItem in tItem)
                {
                    //if (optimisticFieldOut &&  string.Equals(fItem.Name,"LAST_PERSISTENCE",StringComparison.OrdinalIgnoreCase))
                    if (optimisticFieldOut && string.Equals(fItem.Name, lastPAttr.FieldName, StringComparison.OrdinalIgnoreCase))
                    {
                        //Field lastPersistence = new Field("LAST_PERSISTENCE_VERIFY");
                        Field lastPersistence = new Field("LPV");
                        lastPersistence.Value = previousPersistence;
                        preSql.AppendFormat(" AND {0}<={1}",lastPAttr.FieldName,  lastPersistence.GetParameterName());
                        IDataParameter verifyParam = this.CreateParameter();
                        verifyParam.ParameterName = lastPersistence.GetParameterName();
                        verifyParam.Value = lastPersistence.Value;
                        this.Parameters.Add(verifyParam);
                        optimisticFieldOut = false;
                    }
                    if (!idFinded && string.Equals(fItem.Name, idAttr.FieldName, StringComparison.OrdinalIgnoreCase))
                    {
                        preSql.Replace("{2}", fItem.GetParameterName());
                        idFinded = !idFinded;
                    }

                    fields.Append((fields.Length == 0 ? "" + fItem.Name + "=" + fItem.GetParameterName() : "," + fItem.Name + "=" + fItem.GetParameterName()));

                    IDbDataParameter param = this.CreateParameter();
                    param.ParameterName = fItem.GetParameterName();
                    this.DefineParameter(param, fItem);
                    this.Parameters.Add(param);
                }

                if (optimisticFieldOut)
                {
                    throw new InvalidProgramException("The type " + instance.GetType().ToString() + " don't have a last_persistence field in your hierarchy. Only one is needed.");
                }

                StringBuilder sql = new StringBuilder();

                sql.AppendFormat(preSql.ToString(), tItem.Name, fields.ToString());
                DBEngine.EvalReplaceSchema(sql);
                this.CommandText = sql.ToString();

                Configuration config = Configuration.Instance();
                this.ExecuteNonQuery();
                idFinded = !idFinded;
            }


        }

        #region Documentation
        /// <summary>
        /// Exclui uma instância do banco de dados.
        /// </summary>
        /// <param name="instance">instância a ser excluída.</param>
        #endregion Documentation
        internal void Delete(MyObject instance, IDbTransaction trx)
        {
            StringBuilder sql = new StringBuilder();

            this.Parameters.Clear();
            this.Transaction = trx;

            IDbDataParameter param = this.CreateParameter();
            param.ParameterName = new Field("ID").GetParameterName();
            param.Value = instance.Id.ToString();
            this.Parameters.Add(param);

            Type type = instance.GetType();
            IdAttribute idAttr = IdAttribute.GetDefinedAttribute(type);
            Field idField = InternalObjBuilder.CreateFieldToWrite(idAttr, type, instance, "id");

            sql.AppendFormat("DELETE FROM {0} WHERE {2}={1}", MetadataReader.ExtractMainTable(instance.GetType()), param.ParameterName, idAttr.FieldName);
            this.CommandText = sql.ToString().ToUpper();
            this.ExecuteNonQuery();
        }

        #region Documentation
        /// <summary>
        /// PERIGO! Exclui todos os dados relacionados ao tipo passado.
        /// Funciona apenas em modo DEBUG.
        /// </summary>
        /// <param name="type">tipo a ser excluído. deve herdar de MyObject.</param>
        #endregion Documentation
        [System.Diagnostics.Conditional("DEBUG")]
        public void DeleteAll(Type type)
        {
            if (!type.IsSubclassOf(typeof(MyObject))) throw new ArgumentException("DBEngine.DeleteAll().Type passed don't be a subclass of MyObject.");
            try
            {
                StringBuilder sql;
                foreach (string name in MetadataReader.GetSignTables(type))
                {
                    sql = new StringBuilder("DELETE FROM " + name);

                    this.CommandText = sql.ToString();
                    this.ExecuteNonQuery();
                }
            }
            catch (Exception excep)
            {
                throw new InvalidOperationException("DBEngine.DeleteAll().", excep);
            }
        }
        #endregion Methods

        #region IDisposable Members

        public void Dispose()
        {
            if (!scopedTransactions.ContainsKey(this.conn.GetHashCode()))
            {
                try
                {
                    this.command.Dispose();
                    this.conn.Close();
                }
                catch (Exception except)
                {
                }
                finally
                {
                    this.conn.Dispose();
                }
            }
        }

        #endregion

        #region IDbConnection Members

        public IDbTransaction BeginTransaction(IsolationLevel il)
        {
            lock (this.conn)
            {
                MyDbTransaction myTrx;
                if (System.Transactions.Transaction.Current == null)
                {
                    myTrx = new MyDbTransaction(this.conn.BeginTransaction(il));
                }
                else if (scopedTransactions.ContainsKey(this.conn.GetHashCode()) && scopedTransactions[this.conn.GetHashCode()] != null)
                {
                    return scopedTransactions[this.conn.GetHashCode()];
                }
                else
                {
                    System.Transactions.Transaction.Current.TransactionCompleted += new System.Transactions.TransactionCompletedEventHandler(Current_TransactionCompleted);
                    myTrx = new MyDbTransaction(this);
                    while (!scopedTransactions.TryAdd(this.conn.GetHashCode(), myTrx)) { }
                }

                this.trx = new WeakReference<MyDbTransaction>(myTrx);
                this.trx.Target.Concluded += new EventHandler(Target_Concluded);
                this.Transaction = trx.Target;
                GC.KeepAlive(this.trx); GC.KeepAlive(myTrx);
                return trx.Target;
            }
        }

        private void Target_Concluded(object sender, EventArgs e)
        {
            this.trx = null;
        }

        public IDbTransaction BeginTransaction()
        {
            lock (this.conn)
            {
                MyDbTransaction myTrx;
                if (System.Transactions.Transaction.Current == null)
                {
                    myTrx = new MyDbTransaction(this.conn.BeginTransaction());
                }
                else if (scopedTransactions.ContainsKey(this.conn.GetHashCode()) && scopedTransactions[this.conn.GetHashCode()] != null)
                {
                    return scopedTransactions[this.conn.GetHashCode()];
                }
                else
                {
                    System.Transactions.Transaction.Current.TransactionCompleted += new System.Transactions.TransactionCompletedEventHandler(Current_TransactionCompleted);
                    myTrx = new MyDbTransaction(this);
                    while (!scopedTransactions.TryAdd(this.conn.GetHashCode(), myTrx)) { }
                }

                this.trx = new WeakReference<MyDbTransaction>(myTrx);
                this.trx.Target.Concluded += new EventHandler(Target_Concluded);
                this.Transaction = trx.Target;
                GC.KeepAlive(this.trx); GC.KeepAlive(myTrx);
                return trx.Target;
            }
        }

        private void Current_TransactionCompleted(object sender, System.Transactions.TransactionEventArgs e)
        {
            lock (this.conn)
            {
                MyDbTransaction outerTran = null;
                while (!scopedTransactions.TryRemove(this.conn.GetHashCode(), out outerTran)) { }
            }
            //this.Dispose();
        }

        [Obsolete("This method cannot be invoked, the database selection stay in the config file.",true)]
        public void ChangeDatabase(string databaseName)
        {
            //implementar um pool de servidores
        }

        public void Close()
        {
            this.conn.Close();
        }

        public string ConnectionString
        {
            get
            {
                return this.conn.ConnectionString;
            }
            set
            {
                throw new Exception("The property is readonly.");
            }
        }

        public int ConnectionTimeout
        {
            get { return this.conn.ConnectionTimeout; }
        }

        #region Documentation
        /// <summary>
        /// Retorna um IDbCommand "puro" baseado no provider utilizado, sem adicionais do LightOne.
        /// Para ter acesso aos recursos extras do Light, utilize diretamente o DBEngine.
        /// </summary>
        #endregion Documentation
        public IDbCommand CreateCommand()
        {
            return this.conn.CreateCommand();
        }

        public string Database
        {
            get { return this.conn.Database; }
        }

        public void Open()
        {
            this.conn.Open();
        }

        public ConnectionState State
        {
            get { return this.conn.State; }
        }

        #endregion

        #region IDbCommand Members

        public void Cancel()
        {
            this.command.Cancel();
        }

        public string CommandText
        {
            get
            {
                return this.command.CommandText;
            }
            set
            {
                Configuration config = Configuration.Instance();

                switch (config.Transform)
                {
                    case SqlCaseTransform.Upper:
                        this.command.CommandText = value.ToUpper();
                        break;
                    case SqlCaseTransform.Lower:
                        this.command.CommandText = value.ToLower();
                        break;
                    default:
                        this.command.CommandText = value;
                        break;
                }

                
            }
        }

        public int CommandTimeout
        {
            get
            {
                return this.command.CommandTimeout;
            }
            set
            {
                this.command.CommandTimeout = value;
            }
        }

        public CommandType CommandType
        {
            get
            {
                return this.command.CommandType;
            }
            set
            {
                this.command.CommandType = value;
            }
        }

        #region Documentation
        /// <summary>
        /// Retorna uma a si próprio, ou seja, uma vez que DBEngine é em si uma conexão,
        /// retorna a si mesmo.
        /// Por definição, recebe tanto DBEngines quanto DbConnections.
        /// </summary>
        #endregion
        public IDbConnection Connection
        {
            get
            {
                return this;
            }
            set
            {
                if (value is DBEngine)
                {
                    this.conn = ((DBEngine)value).InnerConnection;
                }
                else
                {
                    this.conn = value;
                }
            }
        }

        public IDbDataParameter CreateParameter()
        {
            return this.command.CreateParameter();
        }
        
        public int ExecuteNonQuery()
        {
            return this.command.ExecuteNonQuery();
        }

        public IDataReader ExecuteReader(CommandBehavior behavior)
        {
            //lock (locker)
            {
                return this.command.ExecuteReader(behavior);
            }
        }

        public IDataReader ExecuteReader()
        {
            //lock (locker)
            {
                return this.command.ExecuteReader();
            }
        }

        public object ExecuteScalar()
        {
            return this.command.ExecuteScalar();
        }

        public IDataParameterCollection Parameters
        {
            get { return this.command.Parameters; }
        }

        public void Prepare()
        {
            this.command.Prepare();
        }

        #region Documentation
        /// <summary>
        /// Transação utilzada em operação transacionada.
        /// </summary>
        #endregion
        public IDbTransaction Transaction
        {
            get
            {
                if (this.trx != null && this.trx.IsAlive)
                {
                    GC.KeepAlive(this.trx);
                    return this.trx.Target;
                }
                return null;
            }
            set
            {
                if (value is MyDbTransaction)
                {
                    this.command.Transaction = ((MyDbTransaction)value).InnerTransaction;
                    this.trx = new WeakReference<MyDbTransaction>(value as MyDbTransaction);
                }
                else
                {
                    this.command.Transaction = value;
                    this.trx = new WeakReference<MyDbTransaction>(new MyDbTransaction(value));
                }
                this.trx.Target.Concluded += new EventHandler(Target_Concluded);
                DBEngineFactory.DefineTransactional(this);
            }
        }

        public UpdateRowSource UpdatedRowSource
        {
            get
            {
                return this.command.UpdatedRowSource;
            }
            set
            {
                this.command.UpdatedRowSource = value;
            }
        }

        public override int GetHashCode()
        {
            return this.conn.GetHashCode();
        }
        #endregion

    }
#endif
}
